//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe 
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.
#include <stdio.h>
#include <math.h>

/* ascii code for the escape key */
#define ESCAPE 27

/* The number of our GLUT window */
int window; 

GLuint loop;
GLuint texture[3];

int light = 0;
int blend=  0;

GLfloat xrot = 0.0f;
GLfloat yrot = 0.0f;
GLfloat xspeed = 0.0f;
GLfloat yspeed = 0.0f;

GLfloat walkbias = 0;
GLfloat walkbiasangle = 0.0f;

GLfloat loopupdown = 0.0f;
const float piover180 = 0.0174532925f;

float heading, xpos, ypos, zpos;

GLfloat camx = 0, camy = 0, camz = 0;
GLfloat therotate;

GLfloat z = 0.0f;

GLfloat LightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat LightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f};

GLuint filter = 0;

typedef struct tagVERTEX
{
    float x, y, z;
    float u, v;
}VERTEX;

typedef struct tagTRIANGLE
{
    VERTEX vertex[3];
}TRIANGLE;

typedef struct tagSECTOR
{
    int numtriangles;
    TRIANGLE* triangle;
}SECTOR;

SECTOR sector1;


typedef struct
{
    unsigned long sizeX;
    unsigned long sizeY;
    char* data;
} Image;

float rad(float angle)
{
    return angle * piover180;
}

void readstr(FILE* f, char* string)
{
    do
    {
        fgets(string, 255, f);
    }while((string[0] == '/') || (string[0] == '\n'));
}

void SetupWorld()
{
    FILE* filein;
    int numtriangles;
    char oneline[255];
    float x, y, z, u, v;
    int triloop, vertloop;

    filein = fopen("Data/lesson10/world.txt", "r");


    readstr(filein, oneline);
    printf("oneline: %s\n", oneline);
    sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);

    sector1.triangle = (TRIANGLE*) malloc(numtriangles * sizeof(TRIANGLE));
    sector1.numtriangles = numtriangles;

    for(triloop = 0; triloop < numtriangles; ++triloop)
    {
        for(vertloop = 0; vertloop < 3; ++vertloop)
        {
            readstr(filein, oneline);
            sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
            sector1.triangle[triloop].vertex[vertloop].x = x;
            sector1.triangle[triloop].vertex[vertloop].y = y;
            sector1.triangle[triloop].vertex[vertloop].z = z;
            sector1.triangle[triloop].vertex[vertloop].u = u;
            sector1.triangle[triloop].vertex[vertloop].v = v;
        }
    }

    fclose(filein);
}



int ImageLoad(char* filename, Image* image)
{
    FILE* file;
    unsigned long size;
    unsigned long i;
    unsigned short int planes;
    unsigned short int bpp;
    char temp;

    if ((file = fopen(filename, "r")) == NULL)
    {
        printf("File Not Found: %s\n", filename);
        return 0;
    }

    fseek(file, 18, SEEK_CUR);

    if((i = fread(&image->sizeX, 4, 1, file)) != 1)
    {
        printf("Error reading width from %s.\n", filename);
        return 0;
    }
    printf("Width of %s: %lu\n", filename, image->sizeX);

    if((i = fread(&image->sizeY, 4, 1, file)) != 1)
    {
        printf("Error reading height from %s.\n", filename);
        return 0;
    }
    printf("Height of %s: %lu\n", filename, image->sizeY);

    size = image->sizeX * image->sizeY * 3;

    if ((i = fread(&planes, 2, 1, file)) != 1)
    {
        printf("Planes from %s is not 1: %u\n", filename, planes);
        return 0;
    }

    if ((i = fread(&bpp, 2, 1, file)) != 1)
    {
        printf("Error reading bpp from %s.\n", filename);
        return 0;
    }

    if(bpp != 24)
    {
        printf("Bpp from %s is not 24: %u\n", filename, bpp);
        return 0;
    }

    fseek(file, 24, SEEK_CUR);

    image->data = (char*)malloc(size);
    if(image->data == NULL)
    {
        printf("Error allocation memory for color-corrected image data");
        return 0;
    }

    if((i = fread(image->data, size, 1, file)) != 1)
    {
        printf("Error readint image data from %s.\n", filename);
        return 0;
    }

    for (i = 0; i < size; i += 3)
    {
        temp = image->data[i];
        image->data[i] = image->data[i+2];
        image->data[i+2] = temp;
    }

    return 1;
}


void LoadGLTextures()
{
    Image* image1;

    image1 = (Image*) malloc(sizeof(Image));
    if(image1 == NULL)
    {
        printf("Error allocating space for image.");
        exit(0);
    }

    if (!ImageLoad("Data/lesson10/mud.bmp", image1))
    {
        exit(1);
    }

    glGenTextures(1, &texture[0]);

    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

    glBindTexture(GL_TEXTURE_2D, texture[2]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image1->sizeX, image1->sizeY, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

    printf("----flag---\n");
    if (image1 != NULL)
    {
        if(image1->data)
        {
            free(image1->data);
        }
        free(image1);
    }
    printf("----flag---\n");
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
    LoadGLTextures();
    glEnable(GL_TEXTURE_2D);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);		// This Will Clear The Background Color To Black
    glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LESS);				// The Type Of Depth Test To Do
    glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
    glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();				// Reset The Projection Matrix

    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

    glMatrixMode(GL_MODELVIEW);

    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
    glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
    glEnable(GL_LIGHT1);
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
    if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
        Height=1;

    glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

  
    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
void DrawGLScene()
{
    GLfloat x_m, y_m, z_m, u_m, v_m;
    GLfloat xtrans = -xpos;
    GLfloat ztrans = -zpos;
    GLfloat ytrans = -walkbias - 0.25f;
    GLfloat sceneroty = 360.0f - yrot;

    int numtriangles;
    int loop_m;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
    glLoadIdentity();				// Reset The View

    glRotatef(loopupdown, 1.0f, 0.0f, 0.0f);
    glRotatef(sceneroty, 0, 1.0f, 0);

    glTranslatef(xtrans, ytrans, ztrans);
    glBindTexture(GL_TEXTURE_2D, texture[filter]);

    numtriangles = sector1.numtriangles;

    for(loop_m = 0; loop_m < numtriangles; ++loop_m)
    {
        glBegin(GL_TRIANGLES);
        glNormal3f(0.0f, 0.0f, 1.0f);
        x_m = sector1.triangle[loop_m].vertex[0].x;
        y_m = sector1.triangle[loop_m].vertex[0].y;
        z_m = sector1.triangle[loop_m].vertex[0].z;
        u_m = sector1.triangle[loop_m].vertex[0].u;
        v_m = sector1.triangle[loop_m].vertex[0].v;
        glTexCoord2f(u_m, v_m); glVertex3f(x_m, y_m, z_m);

        x_m = sector1.triangle[loop_m].vertex[1].x;
        y_m = sector1.triangle[loop_m].vertex[1].y;
        z_m = sector1.triangle[loop_m].vertex[1].z;
        u_m = sector1.triangle[loop_m].vertex[1].u;
        v_m = sector1.triangle[loop_m].vertex[1].v;
        glTexCoord2f(u_m, v_m); glVertex3f(x_m, y_m, z_m);

        x_m = sector1.triangle[loop_m].vertex[2].x;
        y_m = sector1.triangle[loop_m].vertex[2].y;
        z_m = sector1.triangle[loop_m].vertex[2].z;
        u_m = sector1.triangle[loop_m].vertex[2].u;
        v_m = sector1.triangle[loop_m].vertex[2].v;
        glTexCoord2f(u_m, v_m); glVertex3f(x_m, y_m, z_m);
        glEnd();
    }


    // since this is double buffered, swap the buffers to display what just got drawn.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
    /* avoid thrashing this procedure */
    usleep(100);

    switch(key)
    {
    /* If escape is pressed, kill everything. */
        case ESCAPE: 
        /* shut down our window */
        glutDestroyWindow(window); 

        /* exit the program...normal termination. */
        exit(0);                   
        case 'f':
        case 'F':
            printf("F/f pressed; filter is: %d\n", filter);
            filter++;
            if (filter > 2)
            {
                filter = 0;
            }
            printf("Filter is now: %d\n", filter);
        break;
        case 'l':
        case 'L':
        printf("F/f pressed; lighting is: %d\n", light);
        light = light ? 0 : 1;
        if (light)
        {
            glEnable(GL_LIGHTING);
        }
        else
        {
            glDisable(GL_LIGHTING);
        }
        printf("Lighting is now: %d\n", light);
        break;
        case 'b':
        case 'B':
        printf("B/b pressed; blending is: %d\n", blend);
        blend = blend ? 0 : 1;
        if (blend)
        {
            glEnable(GL_BLEND);
            glDisable(GL_DEPTH_TEST);
        }
        else
        {
            glDisable(GL_BLEND);
            glEnable(GL_DEPTH_TEST);
        }
        break;
        default:
        break;
    }
}

void specialKeyPressed (int key, int x, int y)
{
    usleep(100);

    switch(key)
    {
        case GLUT_KEY_RIGHT:
            yrot -= 1.5f;
            break;
        case GLUT_KEY_LEFT:
            yrot += 1.5f;
        case GLUT_KEY_UP:
            xpos -= (GLfloat)sin(heading * piover180) * 0.05f;
            zpos -= (GLfloat)cos(heading * piover180) * 0.05f;
            if (walkbiasangle >= 359.0f)
            {
                walkbiasangle = 0.0f;
            }
            else
            {
                walkbiasangle += 10;
            }
            walkbias = (GLfloat)sin(walkbiasangle * piover180) / 20.0f;
            break;
        case GLUT_KEY_DOWN: 
            xpos += (GLfloat)sin(heading * piover180) * 0.05f;
            zpos += (GLfloat)cos(heading * piover180) * 0.05f;
            if (walkbiasangle <= 1.0f)
            {
                walkbiasangle = 359.0f;
            }
            else
            {
                walkbiasangle -= 10;
            }
            walkbias = (GLfloat)sin(walkbiasangle * piover180) / 20.0f;
            break;

    }
}

int main(int argc, char **argv) 
{  
    /* load our world from disk */
    SetupWorld();
    /* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
       X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
    glutInit(&argc, argv);  

    /* Select type of Display mode:   
       Double buffer 
       RGBA color
       Alpha components supported 
       Depth buffer */  
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

    /* get a 640 x 480 window */
    glutInitWindowSize(640, 480);  

    /* the window starts at the upper left corner of the screen */
    glutInitWindowPosition(0, 0);  

    /* Open a window */  
    window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");  

    /* Register the function to do all our OpenGL drawing. */
    glutDisplayFunc(&DrawGLScene);  

    /* Go fullscreen.  This is as soon as possible. */
    glutFullScreen();

    /* Even if there are no events, redraw our gl scene. */
    glutIdleFunc(&DrawGLScene);

    /* Register the function called when our window is resized. */
    glutReshapeFunc(&ReSizeGLScene);

    /* Register the function called when the keyboard is pressed. */
    glutKeyboardFunc(&keyPressed);

    glutSpecialFunc(&specialKeyPressed);

    /* Initialize our window. */
    InitGL(640, 480);

    /* Start Event Processing Engine */  
    glutMainLoop();  

    return 1;
}

